// Copyright 2011-2021 Tyler Gilbert and Stratify Labs, Inc; see LICENSE.md

#include <errno.h>
#include <fcntl.h>
#include <stddef.h>

#include "device/fifo.h"
#include "sos/debug.h"
#include "sos/events.h"

void fifo_inc_tail(fifo_state_t *state, int size) {
  state->atomic_position.access.tail++;
  if (state->atomic_position.access.tail == size) {
    state->atomic_position.access.tail = 0;
  }
}

void fifo_inc_head(fifo_state_t *state, int size) {
  state->atomic_position.access.head++;
  if (state->atomic_position.access.head == size) {
    state->atomic_position.access.head = 0;
  }

  if (state->atomic_position.access.head == state->atomic_position.access.tail) {
    // set tail to size when full
    state->atomic_position.access.tail = size;
  }
}

int fifo_is_write_ok(fifo_state_t *state, u16 size, int writeblock) {
  int ret = 1;

  if (state->atomic_position.access.tail == size) { // the tail is set to size when the
                                                    // buffer is full
    if (writeblock || (state->o_flags & FIFO_FLAG_IS_READ_BUSY)) {
      // cannot write anymore data at this time
      ret = 0;
    } else {
      // OK to write but it will cause an overflow
      if (state->o_flags & FIFO_FLAG_IS_READ_BUSY) {
        state->o_flags |= FIFO_FLAG_IS_WRITE_WHILE_READ_BUSY;
      }
      fifo_set_overflow(state, 1);
    }
  }

  // return OK or not to write: 1 = OK, 0 = write is blocked
  return ret;
}

int fifo_is_writeblock(fifo_state_t *state) {
  return ((state->o_flags & FIFO_FLAG_SET_WRITEBLOCK) != 0);
}

void fifo_set_writeblock(fifo_state_t *state, int value) {
  if (value) {
    state->o_flags |= FIFO_FLAG_SET_WRITEBLOCK;
  } else {
    state->o_flags &= ~FIFO_FLAG_SET_WRITEBLOCK;
  }
}

int fifo_is_overflow(fifo_state_t *state) {
  return ((state->o_flags & FIFO_FLAG_IS_OVERFLOW) != 0);
}

void fifo_set_overflow(fifo_state_t *state, int value) {
  if (value) {
    state->o_flags |= FIFO_FLAG_IS_OVERFLOW;
  } else {
    state->o_flags &= ~FIFO_FLAG_IS_OVERFLOW;
  }
}

int fifo_read_buffer(
  const fifo_config_t *config,
  fifo_state_t *state,
  char *buf,
  int nbyte) {
  int i;
  u16 size = config->size;
  int read_was_clobbered = 0;
  char *dest_buffer = config->buffer;
  fifo_atomic_position_t atomic_position;
  for (i = 0; i < nbyte; i++) {

    state->o_flags |= FIFO_FLAG_IS_READ_BUSY;
    atomic_position.atomic_access =
      state->atomic_position
        .atomic_access; // cppcheck-suppress[unreadVariable] read as union

    if (atomic_position.access.head != atomic_position.access.tail) {
      if (atomic_position.access.tail == size) {
        // buffer is full -- restore tail position
        atomic_position.access.tail = atomic_position.access.head;
      }
      buf[i] = dest_buffer[atomic_position.access.tail];
      atomic_position.access.tail++;
      if (atomic_position.access.tail == size) {
        atomic_position.access.tail = 0;
      }
      // an interrupt here before the tail is assigned will cause a problem
      state->atomic_position.access.tail = atomic_position.access.tail;
      // an interrupt here is OK because the write can write to the open spot
      if (state->o_flags & FIFO_FLAG_IS_WRITE_WHILE_READ_BUSY) {
        read_was_clobbered = 1;
        // if the read was clobbered the buffer is full
        state->atomic_position.access.tail = size;
      }

      state->o_flags &= ~(FIFO_FLAG_IS_WRITE_WHILE_READ_BUSY | FIFO_FLAG_IS_READ_BUSY);

      if (read_was_clobbered) {
        return i;
      }

    } else {
      state->o_flags &= ~(FIFO_FLAG_IS_WRITE_WHILE_READ_BUSY | FIFO_FLAG_IS_READ_BUSY);
      break;
    }
  }
  return i; // number of bytes read
}

int fifo_write_buffer(
  const fifo_config_t *cfgp,
  fifo_state_t *state,
  const char *buf,
  int nbyte,
  int non_blocking) {
  int i;
  int size = cfgp->size;
  int writeblock = 1;
  if (non_blocking == 0) {
    writeblock = fifo_is_writeblock(state);
  }
  for (i = 0; i < nbyte; i++) {
    if (fifo_is_write_ok(state, size, writeblock)) {
      cfgp->buffer[state->atomic_position.access.head] = buf[i];
      fifo_inc_head(state, size);
    } else {
      break;
    }
  }
  return i; // number of bytes written
}

void fifo_flush(fifo_state_t *state) {
  state->atomic_position.access.head = 0;
  state->atomic_position.access.tail = 0;
  fifo_set_overflow(state, 0);
}

void fifo_getinfo(fifo_info_t *info, const fifo_config_t *config, fifo_state_t *state) {
  // flags are the flags that the driver supports
  info->o_flags =
    FIFO_FLAG_SET_WRITEBLOCK | FIFO_FLAG_IS_OVERFLOW | FIFO_FLAG_INIT | FIFO_FLAG_EXIT;
  info->size = config->size;

  fifo_atomic_position_t atomic_position;
  // grab head and tail atomically in case the operation is interrupted
  atomic_position.atomic_access =
    state->atomic_position.atomic_access; // cppcheck-suppress[unreadVariable]

  if (atomic_position.access.tail == config->size) { // check to see if buffer is full
    info->size_ready = config->size;
  } else if (atomic_position.access.head >= atomic_position.access.tail) {
    info->size_ready = atomic_position.access.head - atomic_position.access.tail;
  } else {
    info->size_ready =
      config->size - atomic_position.access.tail + atomic_position.access.head;
  }
  info->overflow = fifo_is_overflow(state);
  fifo_set_overflow(state, 0);
}

void fifo_data_received(const fifo_config_t *config, fifo_state_t *state) {
  if (state->transfer_handler.read != 0) {
    int bytes_read;
    if (
      (bytes_read = fifo_read_buffer(
         config, state, state->transfer_handler.read->buf,
         state->transfer_handler.read->nbyte))
      >= 0) {
      devfs_execute_read_handler(
        &state->transfer_handler, 0, bytes_read, MCU_EVENT_FLAG_DATA_READY);
    }
  }
}

void fifo_cancel_async_read(fifo_state_t *state) {
  devfs_execute_read_handler(
    &state->transfer_handler, 0, SYSFS_SET_RETURN(EAGAIN), MCU_EVENT_FLAG_CANCELED);
}

void fifo_cancel_async_write(fifo_state_t *state) {
  devfs_execute_write_handler(
    &state->transfer_handler, 0, SYSFS_SET_RETURN(EAGAIN), MCU_EVENT_FLAG_CANCELED);
}

int fifo_data_transmitted(const fifo_config_t *cfgp, fifo_state_t *state) {
  if (state->transfer_handler.write != NULL) {
    int bytes_written;
    if (
      (bytes_written = fifo_write_buffer(
         cfgp, state, state->transfer_handler.write->buf_const,
         state->transfer_handler.write->nbyte, 0))
      >= 0) {
      devfs_execute_write_handler(
        &state->transfer_handler, 0, bytes_written, MCU_EVENT_FLAG_WRITE_COMPLETE);
    }
  }

  return 1; // leave the callback in place ??
}

int fifo_open(const devfs_handle_t *handle) {
  const fifo_config_t *config = handle->config;
  fifo_state_t *state = handle->state;
  return fifo_open_local(config, state);
}

int fifo_ioctl(const devfs_handle_t *handle, int request, void *ctl) {
  const fifo_config_t *config = handle->config;
  fifo_state_t *state = handle->state;
  return fifo_ioctl_local(config, state, request, ctl);
}

int fifo_write(const devfs_handle_t *handle, devfs_async_t *async) {
  const fifo_config_t *config = handle->config;
  fifo_state_t *state = handle->state;
  sos_handle_event(SOS_EVENT_FIFO_WRITE, (void *)handle);
  return fifo_write_local(config, state, async, 1);
}

int fifo_read(const devfs_handle_t *handle, devfs_async_t *async) {
  const fifo_config_t *config = handle->config;
  fifo_state_t *state = handle->state;
  sos_handle_event(SOS_EVENT_FIFO_READ, (void *)handle);
  return fifo_read_local(config, state, async, 1);
}

int fifo_close(const devfs_handle_t *handle) {
  const fifo_config_t *config = handle->config;
  fifo_state_t *state = handle->state;
  return fifo_close_local(config, state);
}

int fifo_open_local(const fifo_config_t *config, fifo_state_t *state) {
  MCU_UNUSED_ARGUMENT(config);
  MCU_UNUSED_ARGUMENT(state);
  return 0;
}

int fifo_close_local(const fifo_config_t *config, fifo_state_t *state) {
  MCU_UNUSED_ARGUMENT(config);
  MCU_UNUSED_ARGUMENT(state);
  return 0;
}

int fifo_ioctl_local(
  const fifo_config_t *config,
  fifo_state_t *state,
  int request,
  void *ctl) {
  fifo_attr_t *attr = ctl;
  mcu_action_t *action = ctl;
  switch (request) {
  case I_FIFO_GETINFO:
    fifo_getinfo(ctl, config, state);
    return 0;
  case I_MCU_SETACTION:

    if (action->handler.callback == 0) {

      if (action->o_events & MCU_EVENT_FLAG_WRITE_COMPLETE) {
        fifo_cancel_async_write(state);
      }

      if (action->o_events & MCU_EVENT_FLAG_DATA_READY) {
        fifo_cancel_async_write(state);
      }

      return 0;
    }

    // fifo doesn't store a local handler so it can't set an arbitrary action
    return SYSFS_SET_RETURN(ENOTSUP);
  case I_FIFO_INIT:
    state->transfer_handler.read = NULL;
    state->transfer_handler.write = NULL;
    /* no break */
  case I_FIFO_FLUSH:
    fifo_flush(state);
    fifo_data_transmitted(config, state); // something might be waiting to write the fifo
    return 0;
  case I_FIFO_SETATTR:

    if( attr->o_flags & FIFO_FLAG_INIT ){
      state->transfer_handler.read = NULL;
      state->transfer_handler.write = NULL;
      fifo_flush(state);
      fifo_data_transmitted(config, state); // something might be waiting to write the fifo
    }

    if (attr->o_flags & FIFO_FLAG_SET_WRITEBLOCK) {
      if (attr->o_flags & FIFO_FLAG_IS_OVERFLOW) {
        fifo_set_writeblock(state, 0);
      } else {
        fifo_set_writeblock(state, 1);
      }
    }

    if (fifo_is_writeblock(state)) {
      // make sure the FIFO is not currently blocked
      fifo_data_transmitted(config, state); // something might be waiting to write the
                                            // fifo
    }

    return 0;
  }
  return SYSFS_SET_RETURN(EINVAL);
}

int fifo_read_local(
  const fifo_config_t *config,
  fifo_state_t *state,
  devfs_async_t *async,
  int allow_callback) {
  int bytes_read;

  DEVFS_DRIVER_IS_BUSY(state->transfer_handler.read, async);

  bytes_read = fifo_read_buffer(
    config, state, async->buf,
    async->nbyte); // see if there are bytes in the buffer
  if (bytes_read == 0) {
    if (
      (async->flags & O_NONBLOCK)
      || (state->atomic_position.access.tail == config->size)) {
      bytes_read = SYSFS_SET_RETURN(EAGAIN);
    }
  } else if ((bytes_read > 0) && allow_callback) {
    // see if anything needs to write the FIFO
    fifo_data_transmitted(config, state);
  }

  if (bytes_read != 0) {
    state->transfer_handler.read = 0;
  }

  return bytes_read;
}

int fifo_write_local(
  const fifo_config_t *config,
  fifo_state_t *state,
  devfs_async_t *async,
  int allow_callback) {
  int bytes_written;
  int non_blocking;

  DEVFS_DRIVER_IS_BUSY(state->transfer_handler.write, async);

  non_blocking = ((async->flags & O_NONBLOCK) != 0);
  bytes_written = fifo_write_buffer(
    config, state, async->buf_const, async->nbyte,
    non_blocking); // see if there are bytes in the buffer
  if (bytes_written == 0) {
    if (non_blocking && fifo_is_writeblock(state)) {
      bytes_written = SYSFS_SET_RETURN(EAGAIN);
    }
  } else if ((bytes_written > 0) && allow_callback) {
    fifo_data_received(config, state);
  }

  if (bytes_written != 0) {
    state->transfer_handler.write = 0;
  }

  return bytes_written;
}
